﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using WeBlog.Interfaces;
using WeBlog.Models;
using WeBlog.Repositories;
using WeBlog.Resources;
using WeBlog.Services;

namespace WeBlog.Controllers {
	public class PostController : ApplicationController
	{
		private readonly IEmailService _emailService;
		private readonly PostRepository _postRepository;

		public PostController( IUnitOfWork unitOfWork, IEmailService emailService )
			: base( unitOfWork )
		{			
			_emailService = emailService;			
			_postRepository = new PostRepository(unitOfWork);		    
		}	    
		
		public ActionResult Index( GridSettings settings )
		{
			var model = new PostListViewModel(settings, UnitOfWork);
			UpdateModel(model);
			return View( model );
		}

		[Authorize, Theme("Admin")]
		public ActionResult Manage(GridSettings settings)
		{
			if (!User.CanManagePosts()) return PermissionDenied();
			var model = new ManagePostsViewModel(settings, UnitOfWork, CurrentUser);            
			TryUpdateModel(model);
			return View(model);
		}		

		private void ProcessPostNotifications( Post post )
		{
			if( post.Published )
			{
				var repository = new UserRepository(UnitOfWork);
				var notifyList = repository.FindUsersInRoles(Role.Admin,Role.Editor)
					.Select(x => x.Email).ToArray();
				_emailService.SendPostPublished(post, notifyList );
			}
		}
		

		#region Details

		public ActionResult Details(string slug)
		{
			var model = new PostDetailsViewModel(UnitOfWork, slug);			

			if (SiteConfiguration.TrackPostVisits) SaveVisit(model.Post); 
			return View(model);
		}

		public ActionResult Permalink(int id)
		{		    
			var post = _postRepository.Find(id);
			if (post == null) return NotFound();
			return RedirectToAction("Details", new {slug = post.Slug});
		}

		[HttpPost]
		public ActionResult Details(int id, FormCollection form )
		{
			var model = new PostDetailsViewModel(UnitOfWork, id);

			if (!model.Post.AreCommentsEnabled)
			{
				TempData.SetStatusMessage(Strings.Comments_AreDisabled, StatusMessageType.Error);

				return RedirectToAction("Details", "Post", new { slug = model.Post.Slug });
			}

			TryUpdateModel(model.Comment, "Comment");            			
			model.Update(CurrentUser,Request);
						
			if (ModelState.IsValid)
			{
				model.SaveComment();
				TempData.SetStatusMessage( model.Comment.CommentStatus == CommentStatuses.Approved
											  ? Strings.Comment_Submitted
											  : Strings.Comment_PendingModeration );
				return RedirectToAction("Details", "Post", new { slug = model.Post.Slug });
			}
						
			return View(model);            
		}

		private void SaveVisit( Post post )
		{
			var repository = new PostRepository(UnitOfWork);
			var toUpdate = repository.Find(post.PostId);

			var visit = new PostVisit { DateTime = DateTime.Now};

			if (Request != null)
			{
				visit.IpAddress = Request.UserHostAddress;
				if (Request.IsAuthenticated && CurrentUser != null)
					visit.VisitorId = CurrentUser.UserId;
			}
			toUpdate.PostVisits.Add( visit );
			UnitOfWork.Commit();
		}

		#endregion

		#region Create		
		[HttpGet, Authorize, Theme("Admin")]
		public ActionResult Create()
		{
			if (!User.CanCreatePost()) return PermissionDenied();

			var model = new PostFormViewModel(UnitOfWork, CurrentUser );            
			return View(model);
		}

		[HttpPost, Authorize, ValidateInput(false), Theme("Admin")]
		public ActionResult Create(Post post, string tagList, int[] categoryIds)
		{			
			if (!User.CanCreatePost()) return PermissionDenied();
			TryUpdateModel(post);            

			post.LastModified = DateTime.Now;
			UpdateTags(post, tagList);
			UpdateCategories(post, categoryIds);

			if (String.IsNullOrWhiteSpace(post.Slug))
				post.Slug = Utils.RemoveIllegalCharacters(post.Title);
			else
			{
			    //remove any trailing whitespaces or PostController won't show the post
			    post.Slug = post.Slug.Trim();
			}

			var repository = new PostRepository(UnitOfWork);
			if( repository.FindAll().Any( x => x.Slug == post.Slug ))
				ModelState.AddModelError("Slug", Strings.Validation_NameAlreadyInUse );

			if (repository.FindAll().Any(x => x.Title == post.Title))
				ModelState.AddModelError("Title", Strings.Validation_NameAlreadyInUse);

			if (ModelState.IsValid) {
				repository.Add(post);
				UnitOfWork.Commit();                           
				TempData.SetStatusMessage( Strings.Post_SavedSuccessfully );
				ProcessPostNotifications(post);
				return RedirectToAction("Manage");
			}

			var model = new PostFormViewModel(UnitOfWork, CurrentUser );
			model.Post = post;
			return View(model);
		}
		
		#endregion

		#region Edit
		[Authorize, Theme("Admin")]
		public ActionResult Edit(int id)
		{
			var model = new PostFormViewModel(UnitOfWork, CurrentUser, id);
			
			if (!User.CanEditPost(model.Post)) 
				return PermissionDenied();
			
			return View(model);
		}

		[HttpPost, Authorize, Theme("Admin"), ValidateInput(false)] 
		public ActionResult Edit(int id, string tagList, int[] categoryIds)
		{
			
			Post post = _postRepository.Find( id);

			if (!User.CanEditPost(post))
				return PermissionDenied();

			TryUpdateModel(post,"Post");
			post.LastModified = DateTime.Now;
			UpdateTags(post, tagList );
			UpdateCategories(post, categoryIds);

			if (ModelState.IsValid) {
                //remove trailing whitespaces
			    post.Slug = post.Slug.Trim();
				UnitOfWork.Commit();
				ProcessPostNotifications(post);
				TempData.SetStatusMessage(Strings.Post_SavedSuccessfully);
				return RedirectToAction("Manage");
			}

			var model = new PostFormViewModel(UnitOfWork, CurrentUser, id);
			return View(model);
		}

		private void UpdateTags(Post post, string tagsString)
		{
			if (post.Tags == null)
				post.Tags = new List<Tag>();
			else
				post.Tags.Clear();

			// Remove white space
			tagsString = tagsString.Trim();

			// Need to use a more robust expression where (tag1 tag2 'tag3A tag3B') gets 
			// detected and converted to : tag1 tag2 tag3A-tag3B
			string[] tags = Regex.Split(tagsString, @"\W+");

			var tagRepository = new TagRepository(UnitOfWork);
			foreach (var tag in tags)
			{
				if (string.IsNullOrWhiteSpace(tag)) continue;                
				Tag t = tagRepository.FindByName( tag );
				if (t == null) t = new Tag { Name = tag };                    				
				post.Tags.Add(t);
			}
		}

		public void UpdateCategories(Post post, int[] categoryIds)
		{
			var categoryRepository = new CategoryRepository(UnitOfWork);
			if( post.Categories == null ) 
				post.Categories = new List<Category>();
			else 
				post.Categories.Clear();

			if (categoryIds == null) return;
			foreach (int id in categoryIds)
			{
				var category = categoryRepository.FindById(id);
				if( category != null )
					post.Categories.Add(category);
			}
		}
		#endregion

		#region Delete
		[HttpPost, Authorize, Theme("Admin")]
		public JsonResult Delete(int id)
		{
			if (!User.CanDeletePost()) 
				return Json(new JsonResponse { Message = Strings.Validation_PermissionDenied, Success = false });

			try
			{
				var post = _postRepository.Find(id);                
				_postRepository.Remove(post);
				UnitOfWork.Commit();           
				return Json(new JsonResponse { Success = true, Message = String.Empty });
			}
			catch (Exception ex)
			{
				return Json(new JsonResponse { Message = ex.Message, Success = false });
			}            
		}
		#endregion

		#region Post Rating
		[HttpPost]
		public JsonResult SaveRating(int postId, float rating)
		{
			try
			{
				var post = _postRepository.Find(postId);

				if (post.PostRatings == null)
					post.PostRatings = new List<PostRating>();

				bool canVote;

				if (Principal.Identity.IsAuthenticated)
					canVote = !post.PostRatings.Any(x => x.UserId == CurrentUser.UserId)
						&& CanUserVote(post, rating);
				else
					canVote = CanUserVote(post, rating);

				if (canVote == false)
					throw new Exception("Sorry, you already voted.");

				var postRating = new PostRating {
					PostId = postId,
					Rating = rating,
					DateTime = DateTime.Now,
					IpAddress = Request.UserHostAddress
				};
				if (Principal.Identity.IsAuthenticated)
					postRating.UserId = CurrentUser.UserId;
				post.PostRatings.Add(postRating);
				UnitOfWork.Commit();
					
				return Json(new JsonResponse
				{
					Success = true,
					Message = Strings.Vote_Casted,
					Result = new
					{
						AverageScore = post.PostRatings.Average(x => x.Rating).ToString("0.00"),
						Votes = post.PostRatings.Count()
					}
				});
			}
			catch (Exception ex) {
				return Json(new JsonResponse
				{
					Success = false,
					Message = ex.Message					
				});
			}
		}

		private Boolean CanUserVote(Post post, float rating)
		{
			string key = post.PostId.ToString();

			var voteCookie = Request.Cookies["WeBlog-Votes"];
			if( voteCookie == null )
			{
				voteCookie = new HttpCookie("WeBlog-Votes");
				voteCookie.Expires = DateTime.Now.AddYears(10);
				Response.Cookies.Add(voteCookie);
			}

			if (voteCookie[key] != null )
				return false;

			voteCookie[key] = rating.ToString();			
			return true;
		}
		#endregion

		#region Moderation
		public JsonResult Flag(int id)
		{
			var post = _postRepository.Find(id);			
			_emailService.SendPostFlagged(post);

			return Json(new JsonResponse
			{
				Success = true,
				Message = Strings.Post_Flagged_Message,
			});
		}

		#endregion
	}
}
